package com.example.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.example.entity.dto.Account;
import com.example.entity.dto.StoreImage;
import com.example.mapper.AccountMapper;
import com.example.mapper.ImageStoreMapper;
import com.example.service.ImageService;
import com.example.utils.Const;
import com.example.utils.FlowUtils;
import io.minio.*;
import io.minio.errors.*;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.IOUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.OutputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.UUID;

/**
 * 作者:xuSen
 * 日期2025/4/25 12:52
 */
// 使用slf4j日志框架记录日志
// 标注为Spring的Service组件，表明这是一个被Spring管理的Bean
@Slf4j
@Service
public class ImageServiceImpl  extends ServiceImpl<ImageStoreMapper, StoreImage>  implements ImageService{

    // 注入MinioClient实例，用于与Minio服务器进行交互
    @Resource
    private MinioClient minioClient;

    // 注入AccountMapper实例，用于操作Account相关的数据库表
    @Resource
    private AccountMapper accountMapper;
    @Resource
    private FlowUtils flowUtils;
    private final SimpleDateFormat format= new SimpleDateFormat("yyyyMMdd");
    private final SimpleDateFormat format1= new SimpleDateFormat("yyyyMMdd");
    /**
     * 上传用户头像
     * 该方法接收一个multipart文件和用户ID，将文件上传到MinIO对象存储，并更新用户数据库中的头像URL
     * 如果上传和更新操作成功，返回头像的URL；如果失败，返回null
     * @param file 用户上传的头像文件
     * @param id 用户ID，用于确定哪个用户的头像需要更新
     * @return 成功上传后的头像URL，如果上传失败则返回null
     * @throws IOException 如果文件读取过程中发生错误
     */
    @Override
    public String uploadAvatar(MultipartFile file, int id) throws IOException {
        // 生成一个唯一的后缀名，用于避免文件名冲突
        String imageName = UUID.randomUUID().toString().replace("-", "");
        imageName = "/avatar/" + imageName;
        // 构建上传参数，准备将文件上传到MinIO
        PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                .bucket("study") // 指定存储桶名称
                .stream(file.getInputStream(), file.getSize(), -1) // 设置文件流和文件大小
                .object(imageName) // 设置对象名，即文件在MinIO中的路径
                .build();
        try {
            // 执行文件上传操作
            minioClient.putObject(putObjectArgs);
            // 通过用户ID从数据库中获取用户头像地址
            String avatar = accountMapper.selectById(id).getAvatar();
            // 调用方法删除旧的头像文件
            this.deleteOldImage(avatar);
            // 更新用户数据库中的头像URL
            if(accountMapper.update(null, Wrappers.<Account>update()
                    .eq("id",id).set("avatar",imageName))>0){
                return imageName; // 如果更新成功，返回头像URL
            }else {
                return null; // 如果更新失败，返回null
            }
        } catch (Exception e) {
            // 记录上传失败的日志信息
            log.error("上传失败"+e.getMessage(),e);
            return null; // 上传失败，返回null
        }
    }

    /**
     * 上传图片到MinIO对象存储服务
     *
     * @param file 用户上传的图片文件
     * @param id   图片关联的论坛帖子ID
     * @return 返回存储成功的图片路径，如果上传失败或限制超出则返回null
     * @throws IOException 如果文件读取出现异常则抛出IOException
     */
    @Override
    public String uploadImage(MultipartFile file, int id) throws IOException {
        // 构造缓存计数器的键名，用于限制同一帖子的图片上传数量
        String key= Const.FORUM_IMAGE_COUNTER+id;
        // 检查是否达到每小时最多上传20张图片的限制，如果达到则返回null
        if (!flowUtils.limitPeriodCounterCheck(key,20,3600)) {
            return null;
        }
        // 生成唯一的图片名称，避免图片重名
        String imageName= UUID.randomUUID().toString().replace("-", "");

        // 获取当前日期，用于构造图片的存储路径
        Date date = new Date();
        TimeZone timeZone = TimeZone.getTimeZone("Asia/Shanghai");
        // 构造图片的完整存储路径，包括日期和唯一名称
        imageName="/cache/"+format.format(date)+"/"+imageName;

        // 构造上传参数，包括存储桶名称、文件流、文件大小和对象名
        PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                .bucket("study") // 指定存储桶名称
                .stream(file.getInputStream(), file.getSize(), -1) // 设置文件流和文件大小
                .object(imageName) // 设置对象名，即文件在MinIO中的路径
                .build();

        // 尝试上传图片到MinIO
        try {
            minioClient.putObject(putObjectArgs);
            // 如果图片上传成功，保存图片信息到数据库，返回图片路径；否则返回null
            if(this.save(new StoreImage(id ,imageName,date))){
                return imageName;
            }else return null;

        } catch (Exception e) {
            // 记录上传失败的日志
            log.error("上传失败"+e.getMessage(),e);
        }
        // 如果上传失败，返回null
        return null;
    }

    /**
     * 从Minio服务器获取图片
     *
     * @param outputStream 输出流，用于接收从Minio服务器获取的图片数据
     * @param image 图片的名称，用于在Minio服务器的存储桶中唯一标识该图片
     * @throws IOException 当读写数据发生错误时抛出
     * @throws ServerException 当Minio服务器发生错误时抛出
     * @throws InsufficientDataException 当接收到的数据量少于预期时抛出
     * @throws ErrorResponseException 当Minio服务器返回错误响应时抛出
     * @throws NoSuchAlgorithmException 当指定的加密算法不存在时抛出
     * @throws InvalidKeyException 当指定的加密密钥无效时抛出
     * @throws InvalidResponseException 当Minio服务器返回的响应无效时抛出
     * @throws XmlParserException 当解析XML响应发生错误时抛出
     * @throws InternalException 当发生内部错误时抛出
     */
    @Override
    public void fetchImageFromMinio(OutputStream outputStream, String image) throws IOException, ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        // 构建获取对象的参数，指定存储桶和图片名称
        GetObjectArgs test = GetObjectArgs.builder()
                .bucket("study")
                .object(image)
                .build();
        // 使用Minio客户端获取对象（图片）
        GetObjectResponse object = minioClient.getObject(test);
        // 将获取到的图片数据复制到输出流中
        IOUtils.copy(object, outputStream);
    }
    /**
     * 删除Minio中旧的头像文件
     * 当用户更新头像时，调用此方法来删除之前存储在Minio中的旧头像文件
     *
     * @param avatar 头像文件的名称，用于定位Minio中的具体文件
     */
    public void deleteOldImage(String avatar){
        // 检查传入的头像文件名称是否为空，如果为空则直接返回，不执行删除操作
        if(avatar==null||avatar.isEmpty()) return;

        // 构建删除对象的参数，指定存储桶和图片名称
        RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder()
                .bucket("study") // 指定存储桶名称为"study"
                .object(avatar)  // 指定要删除的头像文件名称
                .build();

        // 使用Minio客户端删除对象（图片）
        try {
            minioClient.removeObject(removeObjectArgs);
        } catch (Exception e) {
            // 记录删除失败的日志，包括异常信息
            log.error("删除失败"+e.getMessage(),e);
        }
    }


}

















